﻿using System;
using System.IO;
using System.Net;
using System.Reflection;
using log4net;

namespace FileDistributor.FileTransfer
{
    /// <summary>
    /// Uploads a file to a FTP server asynchronously
    /// </summary>
    public class FtpUpLoader : FtpUploaderBase
    {
        private static readonly ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        public override void Upload(string sourcePath, Uri targetUri, NetworkCredential credential)
        {
            _logger.Debug("Attempting to upload file " + sourcePath + " to " + targetUri);

            var state = new FtpState();
            var request = (FtpWebRequest)WebRequest.Create(targetUri);
            request.Method = WebRequestMethods.Ftp.UploadFile;
            request.Credentials = credential;
 
            // Store the request in the object that we pass into the
            // asynchronous operations.
            state.Request = request;
            state.SourcePath = sourcePath;
            state.TargetUri = targetUri;

            // Get the event to wait on.
            var waitObject = state.OperationComplete;

            // Asynchronously get the stream for the file contents.
            request.BeginGetRequestStream(
                EndGetStreamCallback,
                state
            );

            // Block the current thread until all operations are complete.
            waitObject.WaitOne();

            // The operations either completed or threw an exception.
            var eventArgs = new FtpUploadEventArgs(state);

            if (state.OperationException != null)
            {
                InvokeFailureEventHandlers(eventArgs);
            }
            else
            {
                InvokeSuccessEventHandlers(eventArgs);
            }
        }

        private static void EndGetStreamCallback(IAsyncResult ar)
        {
            var state = (FtpState)ar.AsyncState;

            // End the asynchronous call to get the request stream.
            try
            {
                using (var requestStream = state.Request.EndGetRequestStream(ar))
                {
                    const int bufferLength = 2048;
                    var buffer = new byte[bufferLength];
                    var count = 0;

                    using (var stream = File.OpenRead(state.SourcePath))
                    {
                        int readBytes;
                        do
                        {
                            readBytes = stream.Read(buffer, 0, bufferLength);
                            requestStream.Write(buffer, 0, readBytes);
                            count += readBytes;
                        }
                        while (readBytes != 0);
                    }
                    //_logger.Debug(string.Format("Writing {0} bytes to the stream.", count));
                }
                // Asynchronously get the response to the upload request.
                state.Request.BeginGetResponse(
                    EndGetResponseCallback,
                    state
                );
            }
            // Return exceptions to the main application thread.
            catch (Exception e)
            {
                var exception = new Exception("Could not get the request stream.", e);
                state.OperationException = exception;
                state.OperationComplete.Set();
            }

        }

        // The EndGetResponseCallback method  
        // completes a call to BeginGetResponse.
        private static void EndGetResponseCallback(IAsyncResult ar)
        {
            var state = (FtpState)ar.AsyncState;

            try
            {
                var response = (FtpWebResponse)state.Request.EndGetResponse(ar);
                response.Close();
                state.StatusDescription = response.StatusDescription;
                // Signal the main application thread that 
                // the operation is complete.
                state.OperationComplete.Set();
            }
            // Return exceptions to the main application thread.
            catch (Exception e)
            {
                var exception = new Exception("Error getting response.", e);
                state.OperationException = exception;
                state.OperationComplete.Set();
            }
        }
        

    }



}
